In this tutorial, you have learned the following:
Impostors are objects who's geometric representation has little or no resemblance to what the viewer sees. These typically generate an object procedurally by cutting fragments out to form a shape, and then use normals to do lighting computations on the cut-out.
Fragments can be discarded from within a fragment shader. This prevents the outputs from the shader from being written to the final image.
Ray tracing can be employed by a fragment shader to determine the position and normal for a point. Those values can be fed into the lighting equation to produce a color value.
Fragment shaders can change the depth value that is used for the depth test and is written to the framebuffer.
Geometry shaders are a shader stage between the vertex shader and the rasterizer. They take a primitive as input and return one or more primitives as output.
Try doing these things with the given programs.
The first version of our impostors was a sphere approximation. It was not useful for relatively large spheres, but it could be useful for small ones. However, that approximation did not compute the depth of the fragment correctly. Make a version of it that does.
Change the geometry impostor tutorial to take another vertex input: the
material to use. The vertex shader should pass it along to the geometry
shader, and the geometry shader should hand it to the fragment shader. You
can still use gl_PrimitiveID
as the way to tell the
fragment shader. Regardless of how you send it, you will need to convert the
value to an integer at some point. That can be done with this
constructor-like syntax: int(value_to_convert)
.
This is an introduction to the concept of impostors. Indeed, the kind of ray tracing that we did has often been used to render more complex shapes like cylinders or quadratic surfaces. But impostors are capable of much, much more.
In effect, impostors allow you to use the fragment shader to just draw stuff to an area of the screen. They can be used to rasterize perfect circles, rather than drawing line-based approximations. Some have even used them to rasterize Bézier curves perfectly.
There are other impostor-based solutions. Most particle systems (a large and vibrant topic that you should investigate) use flat-cards to draw pictures that move through space. These images can animate, changing from one image to another based on time, and large groups of these particles can be used to simulate various phenomena like smoke, fire, and the like.
All of these subjects are worthy of your time. Of course, moving pictures through space requires being able to draw pictures. That means textures, which coincidentally is the topic for our next section.
This fragment shader-only directive will cause the outputs of the fragment to be ignored. The fragment outputs, including the implicit depth, will not be written to the framebuffer.
An input to the vertex shader of type int. This is the index of the vertex being processed.
An output from the fragment shader of type float. This
value represents the depth of the fragment. If the fragment shader does
not use this value in any way, then the depth will be written
automatically, using gl_FragCoord.z
. If the fragment
shader writes to it somewhere, then it must ensure that
all codepaths write to it.
A geometry shader output and the corresponding fragment shader input of type int. If there is no geometry shader, then this value will be the current count of primitives that was previously rendered in this draw call. If there is a geometry shader, but it does not write to this value, then the value will be undefined.
A geometry shader input. It is the current count of primitives previously processed in this draw call.
void EmitVertex( | ) ; |
Available on in the geometry shader, when this function is called, all output variables previously set by the geometry shader are consumed and transformed into a vertex. The value of those variables becomes undefined after calling this function.